defmodule Whithat do
	defmodule Error do
		defexception message: "an example error has occurred"
	end

	defmodule Video do
		def download(videoList, aid) when is_list(videoList),do: download(videoList,aid,[])
		def download(videoLink, aid) do
			uri = "https://api.bilibili.com/x/web-interface/view?aid=#{aid}"
			headers = [
				"User-Agent":
					"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
				Accept: "*/*",
				"Accept-Language": "en-US,en;q=0.5",
				"Accept-Encoding": "gzip, deflate, br",
				Range: "bytes=0-",
				Referer: uri,
				Origin: "https://www.bilibili.com",
				Connection: "keep-alive"
			]
			http =
				HTTPoison.get!(videoLink, headers,
					ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}],
					recv_timeout: :infinity
				)
			http.body
		end

		def download([], _, result),do: Enum.map(result, fn item -> Task.await(item,:infinity) end)
		def download([head|tail],aid,result)  do
			task = Task.async(Whithat.Video, :download, [head, aid])
			download(tail,aid,Whithat.listAppend(result,task))
		end

		def streamDownload(links, aid) when is_list(links),do: links |> Enum.map(fn item -> streamDownload(item,aid) end)
		def streamDownload(link, aid) do
			uri = "https://api.bilibili.com/x/web-interface/view?aid=#{aid}"
			headers = [
				"User-Agent":
					"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
				Accept: "*/*",
				"Accept-Language": "en-US,en;q=0.5",
				"Accept-Encoding": "gzip, deflate, br",
				Range: "bytes=0-",
				Referer: uri,
				Origin: "https://www.bilibili.com",
				Connection: "keep-alive"
			]
			Stream.resource(
				fn ->
					HTTPoison.get!(link, headers,
						ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}],
						recv_timeout: :infinity,
						stream_to: self(),
						async: :once
					)
				end,
				fn %HTTPoison.AsyncResponse{id: id} = resp ->
					receive do
						%HTTPoison.AsyncStatus{id: ^id, code: _}->
							HTTPoison.stream_next(resp)
							{[], resp}
						%HTTPoison.AsyncHeaders{id: ^id, headers: _}->
							HTTPoison.stream_next(resp)
							{[], resp}
						%HTTPoison.AsyncChunk{id: ^id, chunk: chunk}->
							HTTPoison.stream_next(resp)
							{[chunk], resp}
						%HTTPoison.AsyncEnd{id: ^id}->
							{:halt, resp}
					end
				end,
				fn resp ->
					:hackney.stop_async(resp.id)
				end)
		end

		def getInfo(aid, p \\ nil) do
			result = Map.new()

			headers = [
				"User-Agent":
					"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0"
			]

			http =
				HTTPoison.get("https://api.bilibili.com/x/web-interface/view?aid=#{aid}", headers,
					ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}]
				)
				|> elem(1)

			json = Jason.decode!(http.body)
			data = json["data"]
			result = Map.put(result, :title, data["title"])

			if p == nil do
				Map.put(result, :videoList, data["pages"])
			else
				if is_list(p) do
					list =
						Enum.map(p, fn item ->
							data["pages"] |> Enum.fetch!(item - 1)
						end)

					Map.put(result, :videoList, list)
				else
					Map.put(result, :videoList, data["pages"] |> Enum.fetch!(p - 1))
				end
			end
		end

		def getInfoByBvid(bvid, p \\ nil),do: getInfo(Whithat.Video.BVid.decode(bvid),p)

		def getLinks(aid, cid, quality, sessdata) do
			headers = [
				"User-Agent":
					"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
				Cookie: "SESSDATA=#{sessdata}",
				Host: "api.bilibili.com"
			]

			http =
				HTTPoison.get!(
					"https://api.bilibili.com/x/player/playurl?cid=#{cid}&avid=#{aid}&qn=#{quality}",
					headers,
					ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}]
				)

			json = Jason.decode!(http.body)

			Enum.map(json["data"]["durl"], fn item ->
				item["url"]
			end)
		end

		def getLinksFromPlay(:aid, aid, quality, sessdata, p) do
			re = ~r/<script>window.__playinfo__=(?<html>.*?)<\/script>/

			headers = [
				"User-Agent":
					"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
				Cookie: "SESSDATA=#{sessdata}"
			]

			try do
				http =
					HTTPoison.get!("https://www.bilibili.com/video/av#{aid}?p=#{p}", headers,
						ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}]
					)

				html = :zlib.gunzip(http.body)
				result = Regex.named_captures(re, html)

				if is_map(result) do
					%{"html" => result} = result
					json = Jason.decode!(result)
					audio = json["data"]["dash"]["audio"]
					video = json["data"]["dash"]["audio"]

					quality =
						for i <- 0..(length(json["data"]["accept_quality"]) - 1), into: %{} do
							{"#{Enum.fetch!(json["data"]["accept_quality"], i)}", i}
						end
						|> Access.get("#{quality}")

					Enum.map(0..1, fn item ->
						case item do
							0 ->
								%{"audio" => Enum.fetch!(audio, quality) |> Access.get("base_url")}

							1 ->
								%{"video" => Enum.fetch!(video, quality) |> Access.get("base_url")}
						end
					end)
				else
					raise Whithat.Error, message: "Error"
				end
			rescue
				HTTPoison.Error -> :error
				Whithat.Error -> :error
				Jason.DecodeError -> :error
			end
		end

		def getLinksFromPlay(:bvid, bvid, quality, sessdata, p) do
			re = ~r/<script>window.__playinfo__=(?<html>.*?)<\/script>/

			headers = [
				"User-Agent":
					"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
				Cookie: "SESSDATA=#{sessdata}"
			]

			http =
				HTTPoison.get("https://www.bilibili.com/video/#{bvid}?p=#{p}", headers,
					ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}]
				)

			if elem(http, 0) == :ok do
				{:ok, http} = http
				html = :zlib.gunzip(http.body)
				result = Regex.named_captures(re, html)

				if is_map(result) do
					%{"html" => result} = result
					json = Jason.decode(result)

					if elem(json, 0) == :ok do
						{:ok, json} = json
						audio = json["data"]["dash"]["audio"]
						video = json["data"]["dash"]["audio"]

						quality =
							for i <- 0..(length(json["data"]["accept_quality"]) - 1), into: %{} do
								{"#{Enum.fetch!(json["data"]["accept_quality"], i)}", i}
							end
							|> Access.get("#{quality}")

						Enum.map(0..1, fn item ->
							case item do
								0 ->
									%{"audio" => Enum.fetch!(audio, quality) |> Access.get("base_url")}

								1 ->
									%{"video" => Enum.fetch!(video, quality) |> Access.get("base_url")}
							end
						end)
					else
						:error
					end
					:error
				else
				end
			else
				:error
			end
		end

		def getLinksInPrivate(aid, cid, quality) do
			entropy = "rbMCKn@KuamXWlPMoJGsKcbiJKUfkPF_8dABscJntvqhRSETg"

			[appkey, sec] =
				String.reverse(entropy)
				|> String.to_charlist()
				|> Enum.map(fn item ->
					item + 2
				end)
				|> to_string
				|> String.split(":")

			uri = "https://api.bilibili.com/x/web-interface/view?aid=#{aid}"
			params = "appkey=#{appkey}&cid=#{cid}&otype=json&qn=#{quality}&quality=#{quality}&type="
			chksum = :crypto.hash(:md5, params <> sec) |> Base.encode16(case: :lower)
			url = "https://interface.bilibili.com/v2/playurl?#{params}&sign=#{chksum}"

			headers = [
				Referer: uri,
				"User-Agent":
					"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0"
			]

			http = HTTPoison.get!(url, headers, ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}])
			json = Jason.decode!(http.body)

			json["durl"]
			|> Enum.map(fn item ->
				item["url"]
			end)
		end

		def getCover(aid), do: getCover(:aid, aid)

		def getCover(_atom, _varible, url) do
			re = ~r/<script>window.__INITIAL_STATE__=(?<html>.*?);.+<\/script>/

			headers = [
				"User-Agent":
					"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"
			]

			http = HTTPoison.get!(url, headers, ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}])
			html = :zlib.gunzip(http.body)

			cover =
				Regex.named_captures(re, html)
				|> Access.get("html")
				|> Jason.decode!()
				|> Access.get("videoData")
				|> Access.get("pic")
				|> HTTPoison.get!(headers, ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}])

			cover.body
		end

		def getCover(atom, varible) when atom == :aid,
			do: getCover(atom, varible, "https://www.bilibili.com/video/av#{varible}")

		def getCover(atom, varible) when atom == :bvid,
			do: getCover(atom, varible, "https://www.bilibili.com/video/#{varible}")

		def getAid(bvid), do: Whithat.Video.BVid.decode(bvid)

		def getBangumiInfo(ep), do: getBangumiInfo(:ep, ep)

		def getBangumiInfo(_atom, _varilbe, url) do
			re = ~r/<script>window.__INITIAL_STATE__=(?<html>.*?);.+<\/script>/

			headers = [
				"User-Agent":
					"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"
			]

			http = HTTPoison.get!(url, headers, ssl: [{:versions, [:"tlsv1.2", :"tlsv1.1", :tlsv1]}])

			json =
				Regex.named_captures(re, :zlib.gunzip(http.body)) |> Access.get("html") |> Jason.decode!()

			# result = {"name"=>info["mediaInfo"]["title"],"videoList"=>info["epList"].map {|i| {"aid"=>i["aid"],"cid"=>i["cid"],"title"=>i["title"],"longTitle"=>i["longTitle"]}}}
			result = %{:name => json["mediaInfo"]["title"]}

			Map.put(
				result,
				:videoList,
				Enum.map(json["epList"], fn item ->
					%{
						:aid => item["aid"],
						:cid => item["cid"],
						:title => item["title"],
						:longTitle => item["longTitle"]
					}
				end)
			)
		end

		def getBangumiInfo(:ep, varible),
			do: getBangumiInfo(nil, varible, "https://www.bilibili.com/bangumi/play/ep#{varible}")

		def getBangumiInfo(:ss, varible),
			do: getBangumiInfo(nil, varible, "https://www.bilibili.com/bangumi/play/ss#{varible}")

		defmodule BVid do
			def decode(bvid) do
				table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"
				tr = for n <- 0..57, into: %{}, do: {String.at(table, n), n}
				s = [11, 10, 3, 8, 4, 6]
				axor = 177_451_812
				add = 8_728_348_608

				r =
					Enum.sum(
						Enum.map(0..5, fn item ->
							Access.get(tr, String.at(bvid, Enum.fetch!(s, item))) *
								Rintim.Function.power(58, item)
						end)
					)

				Rintim.XOR.num_xor(r - add, axor)
			end

			def encode(aid) do
				axor = 177_451_812
				table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"
				add = 8_728_348_608
				x = Rintim.XOR.num_xor(aid, axor) + add
				s = [11, 10, 3, 8, 4, 6]
				# r = ["B","V","1"," "," ","4"," ","1"," ","7"," "," "]
				origin =
					for i <- 0..5, into: %{} do
						{Enum.fetch!(s, i),
						 String.at(table, (x / Rintim.Function.power(58, i)) |> floor |> Integer.mod(58))}
					end

				result =
					Enum.map(2..11, fn item ->
						case item do
							2 -> 1
							5 -> 4
							7 -> 1
							9 -> 7
							_ -> Access.get(origin, item)
						end
					end)
					|> Enum.join("")

				"BV" <> result
			end
		end
	end

	def listAppend(list, item) when is_list(list) and not is_list(item),
		do: [item|Enum.reverse list] |> Enum.reverse

	def listAppend(list, item) when is_list(list) and is_list(item),
		do: (Enum.reverse(item) ++ Enum.reverse(list)) |> Enum.reverse()

	def listUnShift(item , list) when is_list(list) and is_list(item),
		do: (Enum.reverse(item) ++ Enum.reverse(list)) |> Enum.reverse()

	def ensure_list(item) when is_list(item), do: item
	def ensure_list(item) when not is_list(item), do: [item]

	def main(args \\ [])

	def main(args) when is_list(args) do
		try do
			(fn args ->
				if (!is_list(args) || :erlang.length(args)) < 2 do
					raise ArgumentError,"No enough Arguments"
				else
					args
				end
			end).(args)
			|> (fn args ->
				re = ~r/^BV/
				num = Enum.fetch!(args, 0)
				if Regex.named_captures(re, num) do
					{Whithat.Video.BVid.decode(num),num,args}
				else
					{num,String.to_integer(num) |> Whithat.Video.BVid.encode(),args}
				end
			end).()
			|> (fn item ->
				sessdata = "0e14b4a2%2C1601807138%2C8f164*41"
				q = elem(item, 2) |> Enum.fetch!(1)
				info = Whithat.Video.getInfo(elem(item,0))
				{elem(item,0),elem(item,1),info[:title],info[:videoList],q,sessdata}
			end).()
			|> (fn item ->
				IO.puts("Title: #{elem(item,2)}")
				if :erlang.length(elem(item,3)) == 1 do
					IO.puts("Aid: #{elem(item,0)}  Bvid: #{elem(item,1)}  Cid: #{elem(item,3) |> Enum.fetch!(0) |> Access.get("cid")}")
					{
						true, # only one p
						elem(item,0),
						elem(item,2),
						Whithat.Video.getLinks(elem(item,0), elem(item,3) |> Enum.fetch!(0) |> Access.get("cid"), elem(item,4), elem(item,5))
					}
				else
					IO.puts("Aid: #{elem(item,0)}  Bvid: #{elem(item,1)}")
					IO.puts("Pages:")
					Enum.each(elem(item,3),fn item ->
						IO.puts("-- #{item["part"]}  Cid: #{item["cid"]}")
					end)
					[_,_|var] = args
					if :erlang.length(var) > 0 do
						var = Enum.map(var,fn item -> String.to_integer item end)
						IO.puts("Following Pages Will Be Being Downloaded:")
						IO.puts(Enum.join(var,","))
						{false,elem(item,0),elem(item,2),Enum.map(var,fn i ->
							Whithat.Video.getLinks(elem(item,0), elem(item,3) |> Enum.fetch!(i-1) |> Access.get("cid"),elem(item,4), elem(item,5))
						end),Enum.map(var,fn i -> elem(item,3) |> Enum.fetch!(i-1) |> Access.get("part") end)}
					else
						IO.puts("Following Pages Will Be Being Downloaded:")
						IO.puts(1..(:erlang.length(elem(item,3))) |> Enum.join(","))
						{false,elem(item,0),elem(item,2),Enum.map(elem(item,3),fn i ->
							Whithat.Video.getLinks(elem(item,0), i["cid"],elem(item,4), elem(item,5))
						end),Enum.map(elem(item,3),fn i ->
							i["part"]
						end)}
					end
				end
			end).()
			|> (fn item ->
				case elem(item,0) do
					true ->
						format = [
							frames: ["/", "-", "\\", "|"],
							text: "Downloading......",
							done: "Download Done.",
							spinner_color: "\e[35m",
							interval: 100
						]
						ProgressBar.render_spinner(format,fn ->
							elem(item,3)
							|> Video.streamDownload(elem(item,1))
							|> fn [] -> raise "Error"
										[link] ->
											link
											|> Stream.into(File.stream!("#{Regex.replace(~r/\//,elem(item,2),":")}.flv"))
											|> Stream.run
										list ->
											0..(length(list)-1)
											|> Enum.each(fn i ->
												list
												|> Enum.fetch!(i)
												|> Stream.into(File.stream!("#{Regex.replace(~r/\//,elem(item,2),":")} \##{i+1}.flv"))
												|> Stream.run
											end)
									end.()

							#video = Whithat.Video.download(elem(item,3), elem(item,1))
							#if :erlang.length(video) > 1 do
							#	length = :erlang.length(video)
							#	for(i <- 0..(length - 1), do: [i+1, Enum.fetch!(video, i)])
							#	|> Enum.each(fn i ->
							#		File.write("#{elem(item,2)} \##{Enum.fetch!(i, 0)}.flv", Enum.fetch!(i, 1))
							#	end)
							#else
							#	File.write("#{elem(item,2)}.flv", Enum.fetch!(video, 0))
							#end
						end)
					false ->
						Enum.each(0..((:erlang.length elem(item,3)) - 1),fn i ->
							now = elem(item,3) |> Enum.fetch!(i)
							nowtitle = elem(item,4) |> Enum.fetch!(i)
							format = [
								frames: ["/", "-", "\\", "|"],
								text: "Downloading......",
								done: "Downloading Done.",
								spinner_color: "\e[35m",
								interval: 100
							]
							ProgressBar.render_spinner(format, fn ->
								now
								|> Video.streamDownload(elem(item,1))
								|> fn [] -> raise "Error"
											[link] ->
												link
												|> Stream.into(File.stream!("#{Regex.replace(~r/\//,elem(item,2),":")} --#{Regex.replace(~r/\//,nowtitle,":")}.flv"))
												|> Stream.run
											list ->
												0..(length(list)-1)
												|> Enum.each(fn i ->
													list
													|> Enum.fetch!(i)
													|> Stream.into(File.stream!("#{Regex.replace(~r/\//,elem(item,2),":")} --#{Regex.replace(~r/\//,nowtitle,":")} \##{i+1}.flv"))
													|> Stream.run
										end)
									end.()
								end)
						end)
					end
				0
			end).()
		rescue
			_ in ArgumentError ->
				IO.puts("You Must Give Two Arguments")
				1
		end
		|> System.halt()
	end

end

defmodule Rintim do
	defmodule XOR do
		use Bitwise, skip_operators: true

		def char_xor(a, b) do
			char_xor(a, b, "")
		end

		def char_xor("", "", acc), do: acc

		def char_xor(<<a, as::binary>>, <<b, bs::binary>>, acc) do
			char_xor(as, bs, <<acc::binary, bxor(a, b)>>)
		end

		def num_xor(a, b) do
			bxor(a, b)
		end
	end

	defmodule Function do
		def get(arg, index) when is_list(arg) do
			cond do
				index >= length(arg) ->
					:error

				index < 0 ->
					:error

				true ->
					Enum.fetch(arg, index) |> elem(1)
			end
		end

		def power(num,time),do: power(num,time,1)

		defp power(_num, 0, state),do: state
		defp power(num, time, state),do: power(num,time-1,state*num)

		def queens(num, state \\ [])

		def queens(num, state) when is_list(state) do
			for pos <- 0..(num - 1) do
				# i <- 0..(length(state)-1),get(state,i) == :error or (get(state,i) != pos and abs(get(state,i)-pos) != length(state) - i
				if Enum.all?(0..(length(state) - 1), fn i ->
						 cond do
							 get(state, i) == :error -> true
							 get(state, i) != pos and abs(get(state, i) - pos) != length(state) - i -> true
							 true -> false
						 end
					 end) do
					pos
				else
					nil
				end
			end
			|> Enum.filter(fn x -> x != nil end)
			|> Enum.map(fn item ->
				if length(state) == num - 1 do
					Whithat.listAppend(state, item) |> List.to_tuple()
				else
					queens(num, Whithat.listAppend(state, item))
				end
			end)
			|> List.flatten()
		end
	end
end

defmodule Test do
	def nam(a, b, c, tem \\ []) do
		Enum.map(1..9, fn item ->
			case length(tem) do
				3 ->
					tmp = Enum.fetch!(tem, 2)

					case length(Tuple.to_list(tmp)) do
						2 ->
							tmp = Tuple.append(tmp, item)
							tmp3 = elem(tmp, 0) + elem(tmp, 1) + elem(tmp, 2)
							tmp = Enum.fetch!(tem, 1)
							tmp2 = elem(tmp, 0) + elem(tmp, 1) + elem(tmp, 2)
							tmp = Enum.fetch!(tem, 0)
							tmp1 = elem(tmp, 0) + elem(tmp, 1) + elem(tmp, 2)

							if tmp1 / tmp2 == a / b and tmp1 / tmp3 == a / c and tmp2 / tmp3 == b / c do
								{tmp1, tmp2, tmp3}
							else
								nil
							end

						_ ->
							tmp = Tuple.append(tmp, item)
							tem = List.replace_at(tem, 2, tmp)
							nam(a, b, c, tem)
					end

				2 ->
					tmp = Enum.fetch!(tem, 1)

					case length(Tuple.to_list(tmp)) do
						2 ->
							tmp = Tuple.append(tmp, item)
							tem = Whithat.listAppend(tem, tmp)
							nam(a, b, c, tem)

						_ ->
							tmp = Tuple.append(tmp, item)
							tem = List.replace_at(tem, 1, tmp)
							tem = Whithat.listAppend(tem, {})
							nam(a, b, c, tem)
					end

				1 ->
					tmp = Enum.fetch!(tem, 0)

					case length(Tuple.to_list(tmp)) do
						2 ->
							tmp = Tuple.append(tmp, item)
							tem = List.replace_at(tem, 0, tmp)
							tem = Whithat.listAppend(tem, {})
							nam(a, b, c, tem)

						_ ->
							tmp = Tuple.append(tmp, item)
							tem = List.replace_at(tem, 0, tmp)
							nam(a, b, c, tem)
					end

				0 ->
					nam(a, b, c, [{}])
			end
		end)
		|> List.flatten()
		|> Enum.filter(fn x -> x != nil end)
	end

	def water(n) when is_list(n) and :erlang.length(n) > 2 do
		Enum.map(0..(:erlang.length(n) - 1), fn item ->
			Enum.map((item + 1)..(:erlang.length(n) - 1), fn a ->
				if a >= length(n) do
					nil
				else
					if a > item do
						{item, a, Enum.min([Enum.fetch!(n, item), Enum.fetch!(n, a)]) * (a - item)}
					else
						nil
					end
				end
			end)
		end)
		|> List.flatten()
		|> Enum.filter(fn item -> item != nil end)
		|> Enum.map(fn item ->
			elem(item, 2)
		end)
		|> Enum.max()
	end
end
